﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vowei.Core.Models;
using System.IO;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using Vowei.Converters;
using Vowei.Converters.Helpers;
using System.Diagnostics;
using Vowei.Data.Models;
using Vowei.Data.Helpers;
using Vowei.Core;

namespace Vowei.Data.Converters.Excel
{
    public class WorkItemExcelConverter : ExcelConverter<IEnumerable<ITable>>
    {
        private static string _ItemRowTemplateSheetName = "Task Template";
        

        private ITicketStatusManager _statusManager;
        protected ITicketStatusManager StatusManager { get { return _statusManager; } }

        public WorkItemExcelConverter(ITicketStatusManager statusManager) : 
            this("TaskSchedule", statusManager)
        {
        }

        public WorkItemExcelConverter(string sheetName, ITicketStatusManager statusManager) 
            : this(sheetName, statusManager, DefaultTranslator)
        {
        }

        public WorkItemExcelConverter(string sheetName, ITicketStatusManager statusManager, ITranslator translator)
            : base(translator)
        {
            WorkSheetName = sheetName;
            _statusManager = statusManager;
        }

        public override bool CanConvert(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            if (Translator == null)
                throw new InvalidOperationException(DefaultTranslator.T("在从Excel导入数据之前，请指定用户转换日期、数字、货币等格式的区域设置！"));

            var memoryStream = new MemoryStream((int)stream.Length);
            stream.CopyTo(memoryStream);
            // TODO: 需要告诉HSSFWorkbook的作者，不应该在构造函数里关闭作为参数传递进来的stream
            var workbook = new HSSFWorkbook(memoryStream);
            var sheet = GetWorkItemsSheet(workbook);
            var firstRow = GuessFirstRow(sheet, "WBS", false);
            return firstRow >= 0;
        }

        public override IEnumerable<ITable> ConvertFrom(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");
            if (Translator == null)
                throw new InvalidOperationException(DefaultTranslator.T("在从Excel导入数据之前，请指定用户转换日期、数字、货币等格式的区域设置！"));

            var memoryStream = new MemoryStream((int)stream.Length);
            stream.CopyTo(memoryStream);
            var workbook = new HSSFWorkbook(memoryStream);
            var sheet = GetWorkItemsSheet(workbook);
            var firstRow = GuessFirstRow(sheet);

            var map = ReadColumnNames(sheet.GetRow(firstRow), new string[] {
                "WBS",
                FieldName(t => t.Title), // "任务名称",
                FieldName(t => t.AssignedTo), // "指派给",
                Translator.T<Task, DateTime?>(t => t.StartDate), // "起始日期",
                Translator.T<Task, DateTime?>(t => t.DueDate), // "结束日期",
                Translator.T<Requirement, float>(t => t.BusinessValue),
                FieldName(t => t.OpenDate),
                FieldName(t => t.ClosedDate),
                FieldName(t => t.Priority),
                FieldName(t => t.Status),
                FieldName(t => t.Progress) // "% 完成百分比",
            }, 
            0, 256);
            var result = new List<ITable>();
            ITable lastItem = null;
            Milestone currentMilestone = null;
            var requirements = new Stack<Requirement>();
            var wbses = new Stack<string>();

            for (int i = (firstRow + 1); i < sheet.LastRowNum + 1; ++i)
            {
                var row = sheet.GetRow(i);

                try
                {
                    var wbs = ReadCell<string>(row, 0, false);

                    // 任务列表结束
                    if (string.IsNullOrEmpty(wbs))
                        break;          
                    else // 解决需求与任务之间的复杂层次关系         
                        PopRequirementsAsNeccessary(requirements, wbses, wbs);

                    if (IsMilestone(wbs))
                    {
                        currentMilestone = ReadMilestoneFrom(row, map);
                        lastItem = currentMilestone;
                    }
                    else if (IsRequirement(wbs, sheet, i, requirements.Count != 0) )
                    {
                        var requirement = ReadRequirementFrom(row, map, currentMilestone);
                        lastItem = requirement;
                        
                        if (requirements.Count > 0)
                        {
                            var parent = requirements.Peek();
                            parent.Children.Add(requirement);
                        }

                        requirements.Push(requirement);
                        wbses.Push(wbs);
                    }
                    else
                    {
                        var task = ReadTaskFrom(row, map);
                        task.BelongsToMilestone = currentMilestone;
                        lastItem = task;

                        if (requirements.Count == 0)
                            throw new InvalidDataException(string.Format(Translator.T("在试图导入的Excel文件的{0}的页签里，第{1}行中的第{2}个单元格里，任务{3}没有任何上级需求与其对应"), WorkSheetName, i + 1, map[FieldName(t => t.AssignedTo)], ((Task)lastItem).Title));

                        var requirement = requirements.Peek();
                        requirement.Children.Add((Ticket)lastItem);
                    }

                    result.Add(lastItem);
                }
                catch (Exception e)
                {
                    // throw new InvalidDataException(string.Format(Translator.T("在试图导入Excel文件的“{0}”工作表失败，第{1}行有误, 具体错误信息是：{2}"), WorkSheetName, i + 1, e.Message), e);
                    Trace.TraceError(string.Format(Translator.T("在试图导入Excel文件的“{0}”工作表失败，第{1}行有误, 具体错误信息是：{2}"), WorkSheetName, i + 1, e.Message), e);
                }
            }

            return result;
        }

        private int GuessFirstRow(Sheet sheet)
        {
            return GuessFirstRow(sheet, "Wbs");
        }

        public override void ConvertTo(IEnumerable<ITable> works, Stream stream)
        {
            if (string.IsNullOrEmpty(TemplateExcel))
                throw new InvalidOperationException(Translator.T("在生成Excel文件之前,请通过设置TemplateExcel属性指定输出任务Wbs的Excel文件的模板！"));

            using (var template = new FileStream(TemplateExcel, FileMode.Open, FileAccess.Read))
            {
                using (var workbook = new HSSFWorkbook(template))
                {
                    CreateSummaryInfo(workbook);

                    Row milestoneTmplRow = null;
                    Row[] ticketTmplRows = new Row[4] { null, null, null, null };

                    ReadTemplateRowsFrom(workbook, ticketTmplRows, out milestoneTmplRow);
                    WriteWorksItems(workbook, works, milestoneTmplRow, ticketTmplRows);

                    workbook.Write(stream);
                }
            }
        }

        private void ReadTemplateRowsFrom(HSSFWorkbook workbook, Row[] ticketTmplRows, out Row milestoneTmplRow)
        {
            var sheet = workbook.GetSheet(_ItemRowTemplateSheetName);
            if (sheet == null)
            {
                throw new InvalidDataException(string.Format(
                    Translator.T("指定的Wbs工作任务Excel模板文件{0}里,没有包含有里程碑以及任务模板行的工作表{1}."), TemplateExcel, _ItemRowTemplateSheetName));
            }

            var templates = new List<Row>();
            // 跳过表头
            for (var i = sheet.FirstRowNum + 1; i <= sheet.LastRowNum; ++i)
            {
                var row = sheet.GetRow(i);
                templates.Add(row);
            }

            milestoneTmplRow = templates[0];
            templates.CopyTo(1, ticketTmplRows, 0, ticketTmplRows.Length);
        }

        private void WriteWorksItems(HSSFWorkbook workbook, IEnumerable<ITable> works, Row milestoneTmplRow, Row[] ticketTmplRows)
        {
            var sheet = workbook.GetSheet(WorkSheetName);
            var project = works.ElementAt(0) as Project;
            if (project == null)
                throw new ArgumentException(Translator.T("第一个元素必须要是表示项目[实现了Project接口]的对象!"), "works");
            WriteProjectInfo(sheet, project);

            var itemStartIdx = 9;
            for (int i = 1, currentRow = i - 1 + itemStartIdx; i < works.Count(); ++i, ++currentRow)
            {
                // 跳过列头
                var row = sheet.CreateRow(currentRow);
                var item = works.ElementAt(i) as INamedTable;

                if (string.IsNullOrEmpty(item.Title))
                {
                    // throw new InvalidDataException(string.Format(Translator.T("工作任务或者里程碑或需求 [{0}] 没有标题！"), item.ID));
                    Trace.TraceError(string.Format(Translator.T("工作任务或者里程碑或需求 [{0}] 没有标题！"), item.ID));
                }

                if (item is Milestone)
                    WriteMilestone((Milestone)item, row, milestoneTmplRow);
                else if (item is Requirement)
                    WriteRequirement(sheet, (Requirement)item, row, ticketTmplRows, 0, ref currentRow);
                else if (item is Task)
                    WriteTask((Task)item, row, ticketTmplRows[0]);
            }
        }

        private void WriteRequirement(Sheet sheet, Requirement req, Row row, Row[] templates, int tmplIdx, ref int currentRow)
        {
            var template = templates[tmplIdx];
            template.CopyTo(row);
            SetCellValue(row, 1, req.Title);

            // 进度
            row.GetCell(6).SetCellValue(req.Progress);

            tmplIdx = (tmplIdx < templates.Length - 1) ? tmplIdx + 1 : tmplIdx; 
            foreach (var child in req.Children)
            {
                currentRow++;
                if (child is Requirement)
                    WriteRequirement(sheet, (Requirement)child, sheet.CreateRow(currentRow), templates, tmplIdx, ref currentRow);
                else if (child is Task)
                    WriteTask((Task)child, sheet.CreateRow(currentRow), templates[tmplIdx]);
                else
                    throw new NotSupportedException(string.Format(Translator.T("不支持类型为 [{0}]　的任务！"), EntityHelpers.GetEntityTypeName(child.GetType())));
            }
        }

        private void WriteTask(Task task, Row row, Row template)
        {
            template.CopyTo(row);
            SetCellValue(row, 1, task.Title);

            if ( task.StartDate.HasValue )  
                row.GetCell(3).SetCellDateTimeValue(task.StartDate.Value);

            if ( task.DueDate.HasValue )
                row.GetCell(4).SetCellDateTimeValue(task.DueDate.Value);

            row.GetCell(6).SetCellValue(task.Progress);
        }

        private void WriteMilestone(Milestone item, Row row, Row template)
        {
            // 创建新的一行
            template.CopyTo(row);

            SetCellValue(row, 1, item.Title);

            if (item.StartDate.HasValue)
                row.GetCell(3).SetCellDateTimeValue(item.StartDate.Value);
            if (item.DueDate.HasValue)
                row.GetCell(4).SetCellDateTimeValue(item.DueDate.Value);
        }

        private void SetCellValue(Row row, int columnIdx, string value)
        {            
            var cell = row.GetCell(columnIdx);

            Debug.Assert(cell.CellType == CellType.STRING,
                string.Format(Translator.T("在将要导出的Excel文件的{0}行，{1}列，单元格不是一个字符串格式的单元格！"), row.RowNum + 1, columnIdx + 1));

            cell.SetCellValue(value);
        }

        private void CreateSummaryInfo(HSSFWorkbook workbook)
        {
            // TODO: 考虑是否要将GetDocumentSummaryInformation和GetSummaryInformation放到基类里面去
            //workbook.DocumentSummaryInformation = GetDocumentSummaryInformation(_currentUser);
            //workbook.SummaryInformation = GetSummaryInformation();
        }

        private void WriteProjectInfo(Sheet sheet, Project project)
        {
            // A1:B1 - 项目名称
            var row = sheet.GetRow(0);
            if (row == null)
                throw new InvalidOperationException(Translator.T("在指定的工作任务Excel文件模板里, 必须要有A1:B1这个单元格!"));

            var cell = row.GetCell(0);
            if (cell == null)
                throw new InvalidOperationException(Translator.T("在指定的工作任务Excel文件模板里, 必须要有A1:B1这个单元格!"));

            cell.SetCellValue(project.Title);

            // C5
            if (project.StartDate != null)
            {
                row = sheet.GetRow(4);
                if (row == null)
                    throw new InvalidOperationException(Translator.T("在指定的工作任务Excel文件模板里, 必须要有C5这个单元格!"));

                cell = row.GetCell(2);
                if (cell == null)
                    throw new InvalidOperationException(Translator.T("在指定的工作任务Excel文件模板里, 必须要有C5这个单元格!"));

                cell.SetCellValue(project.StartDate.Value);
            }
        }
        
        private void PopRequirementsAsNeccessary(Stack<Requirement> requirements, Stack<string> wbses, string wbs)
        {
            while (requirements.Count > 0)
            {
                var iter = wbses.Peek();
                if (CompareWbs(iter, wbs) <= 0)
                {
                    wbses.Pop();
                    requirements.Pop();
                }
                else
                    break;
            }
        }

        private bool IsRequirement(string wbs, Sheet sheet, int i, bool hasRemainingReq)
        {
            // 最后一行，要么是里程碑，要么就是任务
            if (i >= sheet.LastRowNum)
                return false;
            else if (hasRemainingReq)
            {
                var nextWbs = ReadCell<string>(sheet.GetRow(i + 1), 0);
                return CompareWbs(nextWbs, wbs) < 0;
            }
            else // 如果已经没有现成需求， 默认就当作需求处理
                return true;
        }

        private int CompareWbs(string nextWbs, string wbs)
        {
            var ls = nextWbs.Split('.');
            var rs = wbs.Split('.');

            if (ls.Length < rs.Length)
                return 1;
            else if (ls.Length > rs.Length)
                return -1;
            else
                return 0;
        }

        private Milestone ReadMilestoneFrom(Row row, Dictionary<string, int> map)
        {
            var milestone = new Milestone()
            {
                Title = ReadCell<string>(row, map[Translator.T<Milestone, string>(m => m.Title)]),
                StartDate = ReadDateCell(row, map[Translator.T<Milestone, DateTime?>(m => m.StartDate)]),
                DueDate = ReadDateCell(row, map[Translator.T<Milestone, DateTime?>(m => m.DueDate)]),
                Progress = ReadCell<float>(row, map[FieldName(t => t.Progress)], false),
                ClosedDate = ReadDateCell(row, map[FieldName(t => t.ClosedDate)])
            };

            var value = ReadDateCell(row, map[FieldName(t => t.OpenDate)]);
            if (value.HasValue)
                milestone.OpenDate = value.Value;

            milestone.LastModified = DateTime.Now;

            return milestone;                       
        }

        private Requirement ReadRequirementFrom(Row row, Dictionary<string, int> map, Milestone milestone)
        {
            var req = new Requirement()
            {
                Title = ReadCell<string>(row, map[FieldName(t => t.Title)]),
                BelongsToMilestone = milestone,
                Progress = ReadCell<float>(row, map[FieldName(t => t.Progress)], false),
                AssignedTo = ReadCell<string>(row, map[FieldName(t => t.AssignedTo)], false),
                BusinessValue = ReadCell<float>(row, map[Translator.T<Requirement, float>(t => t.BusinessValue)], false),
                ClosedDate = ReadDateCell(row, map[FieldName(t => t.ClosedDate)])
            };

            if (req.Progress == 1.0f)
            {
                if (req.ClosedDate.HasValue)
                    StatusManager.Close(req, req.ClosedDate.Value);
                else
                    StatusManager.Close(req);
            }

            var value = ReadDateCell(row, map[FieldName(t => t.OpenDate)]);
            if (value.HasValue)
                req.OpenDate = value.Value;

            req.LastModified = DateTime.Now;

            return req;
        }

        private Task ReadTaskFrom(Row row, Dictionary<string, int> map)
        {
            var task = new Task()
            {
                Title = ReadCell<string>(row, map[FieldName(t => t.Title)]),
                StartDate = ReadDateCell(row, map[Translator.T<Task, DateTime?>(m => m.StartDate)]),
                DueDate = ReadDateCell(row, map[Translator.T<Task, DateTime?>(m => m.DueDate)]),
                Progress = ReadCell<float>(row, map[FieldName(t => t.Progress)], false),
                AssignedTo = ReadCell<string>(row, map[FieldName(t => t.AssignedTo)], false),
                ClosedDate = ReadDateCell(row, map[FieldName(t => t.ClosedDate)])
            };

            if ( !task.StartDate.HasValue )
            {
                //throw new InvalidOperationException(string.Format(
                //    Translator.T("正在导入的任务[{0}]没有指定必填的属性[{1}]，放弃导入该任务"), task.Title, 
                //    Translator.T<Task, DateTime?>(m => m.StartDate)));
                var now = DateTime.Now;
                task.StartDate = now;
                Trace.TraceError(string.Format(
                   Translator.T("正在导入的任务[{0}]没有指定必填的属性[{1}]，系统正常导入该任务，并将任务的[{1}]设置为当前时间：[{2}]"), 
                   task.Title,
                   Translator.T<Task, DateTime?>(m => m.StartDate), Translator.T(now)));
            }

            // 如果任务的完成百分比为100%，并且要导入的Excel文件里没有关闭任务的日期信息
            // 那么就使用任务的计划结束日期作为任务的关闭日期。
            if (task.Progress == 1.0f)
            {
                if ( task.DueDate.HasValue && 
                     map[FieldName(t => t.ClosedDate)] < 0)
                {
                    StatusManager.Close(task, task.DueDate.Value);
                }
                else if ( task.ClosedDate.HasValue )
                {                    
                    StatusManager.Close(task, task.ClosedDate.Value);
                }
                else
                {
                    StatusManager.Close(task);
                }
            }

            var value = ReadDateCell(row, map[FieldName(t => t.OpenDate)]);
            if (value.HasValue)
                task.OpenDate = value.Value;

            task.LastModified = DateTime.Now;

            return task;
        }

        private bool IsMilestone(string wbs)
        {
            // WBS里，里程碑是没有小数点分隔的，对于这种Excel模板，没有子里程碑的概念
            return !wbs.Contains('.');
        }
    }
}
